# -*- coding: utf-8 -*-

###############################################################################
#
# Copyright (C) 2006 Maciej Wisniowski pigletto@gmail.com
#
# This module is part of RAMCacheManagerPlus and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
#
# Or, if you want this program may be distributed under the
# Zope Public License (ZPL) Version 2.1, as published on the Zope web site,
# http://www.zope.org/Resources/ZPL.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
# or FITNESS FOR A PARTICULAR PURPOSE.
#
# See the LICENSE file for details.
#
###############################################################################
""" RAMCacheManagerPlus is a simple extension of RAMCacheManager that makes
    it possible to flush individual cache entries.

    $Id: RAMCacheManagerPlus.py 7 2006-11-23 20:44:56Z pigletto $
"""

from Acquisition import Explicit

__doc__="""RAMCacheManagerPlus Product"""
__version__='1.0'


from zope.interface import implements
from zope.interface import Interface

from interfaces import IRAMCachePlus, ICacheablePlus

import logging
logger = logging.getLogger('RAMCacheManagerPlus')


class RAMCachePlus:
    implements(IRAMCachePlus)

    def __init__(self, context):
        self.context = context

    def aggregateIndex(self, view_name, keywords_req, keywords_local):
        '''
        Returns the index to be used when looking for or inserting
        a cache entry. Index is created from parameters but should
        return same value as original index from:
           RAMCacheManager.ObjectCacheEntries.aggregateIndex

        view_name is a string.
        keywords_req and keywords_local are dictionaries
        '''
        req_index = []
        # req_names have to be sorted
        req_keys = keywords_req.keys()
        req_keys.sort()
        for key in req_keys:
            req_index.append((str(key), str(keywords_req[key])))

        if keywords_local:
            local_index = []
            for key, val in keywords_local.items():
                local_index.append((str(key), str(val)))
            local_index.sort()
        else:
            local_index = ()
        return (str(view_name), tuple(req_index), tuple(local_index))

    def invalidate(self, ob, view_name='', keywords_req={},
                   keywords_local={}, partial=0):
        ''' Invalidates the cache entry that applies to ob and specific key.
        '''
        try:
            self.context.writelock.acquire()
            # get all cache entries for current object
            oc = self.context.getObjectCacheEntries(ob, create=0)
            if oc is None:
                return None

            # Generate index of cache entry that we'll flush
            index = self.aggregateIndex(view_name,
                                        keywords_req,
                                        keywords_local)

            # if partial then we may flush cache entries that match
            # invalidate partially matched indexes
            if partial:
                # list of entries that matched our index
                entries2del = []
                logger.debug('entries: %s' % (oc.entries.keys()))
                for entry_key in oc.entries.keys():
                    # check whether view_names matches
                    if entry_key[0] != index[0]:
                        continue

                    match = 1

                    # check whether keywords_req are contained in entry_key
                    entry_req_names = str(entry_key[1])
                    for index_req_name in index[1]:
                        if not str(index_req_name) in entry_req_names:
                            match = 0
                            break

                    # check whether keywords_local are contained in entry_key
                    entry_local_keys = str(entry_key[2])
                    for index_local_key in index[2]:
                        if not str(index_local_key) in entry_local_keys:
                            match = 0
                            break

                    # if entry key contains all params then it'll be flushed
                    if match:
                        entries2del.append(entry_key)

                for entry in entries2del:
                    logger.debug('flushing: %s' % (str(entry)))
                    oc.delEntry(entry)

                if entries2del:
                    return 'Invalidated'
            else:
                if oc and oc.entries.has_key(index):
                    logger.debug('flushing: %s' % (str(index)))
                    oc.delEntry(index)
                    return 'Invalidated'
                else:
                    return 'Nothing to invalidate'
        finally:
            self.context.writelock.release()
        return 'Nothing to invalidate'


class CacheablePlus(Explicit):
    implements(ICacheablePlus)

    def __init__(self, context):
        self.context = context

    def invalidate(self, view_name='', keywords_req={}, keywords_local={},
                   partial=0, REQUEST=None):
        c = self.context.ZCacheable_getCache()
        if c is not None:
            ob, view_name = self.context.ZCacheable_getObAndView(view_name)
            try:
                ac = RAMCachePlus(c)
                message = ac.invalidate(ob,
                                                view_name,
                                                keywords_req,
                                                keywords_local,
                                                partial)
                if not message:
                    message = 'Invalidated.'
            except Exception, err:
                logger.warning('ZCache_invalidate() exception: %s' % (err))
        else:
            message = 'This object is not associated with a cache manager.'
        return message


def CacheablePlusFactory(context):
    """ Factory function that is necessary in order to
        make adapter useage possible directly in content
        space, eg. from Script (Python)
    """
    return CacheablePlus(context).__of__(context)